//
//  BMKLocationManagerDelegate+Rx.swift
//  RxObjectDemo
//
//  Created by CaryaLiu on 2021/2/22.
//

import RxSwift
import RxCocoa

/// extension wrapper With Base class `BMKLocationManager`
/// Wraps events coming form `BMKLocationManagerDelegate`
extension Reactive where Base: BMKLocationManager {
    /// Reactive wrapper for `BMKLocationManagerDelegate`.
    public var delegate: RxBMKLocationManagerDelegate {
        return RxBMKLocationManagerDelegateProxy.proxy(for: base)
    }
    
    /// Reactive wrapper for `func bmkLocationManager(_ manager: BMKLocationManager, didChangeAuthorization status: CLAuthorizationStatus)` before iOS 14,
    /// Reactive wrapper for `func bmkLocationManagerDidChangeAuthorization(_ manager: BMKLocationManager)` after iOS 14
    public var didChangeAuthorization: ControlEvent<BMKAuthorizationEvent> {
        var source: Observable<BMKAuthorizationEvent>
        if #available(iOS 14, *) {
            source = delegate.methodInvoked(.didChangeAuthorization).map(authorizationStatus(_:))
        } else {
            source = delegate.methodInvoked(.didChangeAuthorizationStatus).map(authorizationStatus(_:))
        }
        return ControlEvent(events: source)
    }
    
    /// Reactive wrapper for: CLErrorEvent
    /// `func bmkLocationManager(_ manager: CLLocationManager, didFailWithError error: Error)`
    public var didFailError: ControlEvent<BMKErrorEvent> {
        let error = delegate.methodInvoked(.didFailWithError).map(errorEvent(_:))
        return ControlEvent(events: error)
    }
    
    /// Reactive wrapper for 
    /// `func bmkLocationManager(_ manager: BMKLocationManager, didUpdate location: BMKLocation?, orError error: Error?)`
    public var didUpdateLocation: ControlEvent<BMKLocationEvent> {
        let location = delegate.methodInvoked(.didUpdateLocation).map(updateLocationEvent(_:))
        return ControlEvent(events: location)
    }
    
    /// Reactive wrapper for
    /// `func bmkLocationManager(_ manager: BMKLocationManager, doRequestAlwaysAuthorization locationManager: CLLocationManager)`
    public var doRequestAlwaysAuthorization: ControlEvent<BMKRequestAlwaysAuthorizationEvent> {
        let event = delegate.methodInvoked(.doRequestAlwaysAuthorization).map(doRequestAlwaysAuthorizationEvent(_:))
        return ControlEvent(events: event)
    }
    
    /// Reactive wrapper for
    /// `func bmkLocationManager(_ manager: BMKLocationManager, didUpdate heading: CLHeading?)`
    public var didUpdateHeading: ControlEvent<BMKUpdateHeadingEvent> {
        let heading = delegate.methodInvoked(.didUpdateHeading).map(didUpdateHeadingEvent(_:))
        return ControlEvent(events: heading)
    }
    
    public var didUpdateNetworkState: ControlEvent<BMKUpdateNetworkStateEvent> {
        let networkState = delegate.methodInvoked(.didUpdateNetworkState).map(didUpdateNetworkState)
        return ControlEvent(events: networkState)
    }
    
    private func authorizationStatus(_ args: [Any]) throws -> BMKAuthorizationEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let status = manager.authorizationStatus()
        return (manager, status)
    }
    
    private func errorEvent(_ args: [Any]) throws -> BMKErrorEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let error = try castOrThrow(Error.self, args[1])
        return (manager, error)
    }
    
    private func updateLocationEvent(_ args: [Any]) throws -> BMKLocationEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let location = try castOrThrow(BMKLocation.self, args[1])
        if let error = args[2] as? Error {
            return (manager, location, error)
        }
        return (manager, location, nil)
    }
    
    private func didUpdateHeadingEvent(_ args: [Any]) throws -> BMKUpdateHeadingEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let heading = try castOrThrow(CLHeading.self, args[1])
        return (manager, heading)
    }
    
    private func didUpdateNetworkState(_ args: [Any]) throws -> BMKUpdateNetworkStateEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let networkState = try castOrThrow(BMKLocationNetworkState.self, args[1])
        if let error = args[2] as? Error {
            return (manager, networkState, error)
        }
        return (manager, networkState, nil)
    }
    
    private func doRequestAlwaysAuthorizationEvent(_ args: [Any]) throws -> BMKRequestAlwaysAuthorizationEvent {
        let manager = try castOrThrow(BMKLocationManager.self, args[0])
        let locationManager = try castOrThrow(CLLocationManager.self, args[1])
        return (manager, locationManager)
    }
}
